關於 BabyEditorActivity 的實作細節,我們還有兩個細節需要討論:(1) RadioGroup 的使用;(2) 如何從 SD 卡的相片或透過拍攝來設定大頭貼。然而在介紹這兩個議題之前,筆者想先暫時停止「新增寶寶」的實作,改成先實作「寶寶列表」,原因是這樣我們可以做一些簡單的測試,在昨天的程式碼中,讀者應該不難發現「完成」按鈕按下後會呼叫 BabyDB.addBaby 將寶寶的資料儲存到資料庫,如果堅持要完成所有「新增寶寶」的功能後再實作「寶寶列表」的功能,依筆者現在的開發進度,可能要好幾天後才能測試寶寶資料是否有正確儲存。
基於上述的理由,今天我們來討論「寶寶列表」的實作,寶寶列表會使用到 ListView,這個 ListView 跟我們已介紹過的 View (如TextView, ImageView, Button等)有一點點的不同,ListView繼承了AdapterView,繼承 AdapterView 的介面元件的特性是:「資料」和「介面元件」需要透過 Adapter做結合 [1]。
我們就實際用「寶寶生活記錄 App」的「寶寶列表 Activity」 來解說 ListView 的使用方法,首先這個 Activity 的版面設計檔如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="6dp"
android:paddingLeft="7dp"
android:paddingRight="7dp"
android:id="@+id/rootview"
android:paddingTop="5dp"
android:background="#EBEBEB" >
<LinearLayout android:id="@+id/ll_loading"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:gravity="center"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:textSize="16sp"
android:text="@string/loading"
/>
</LinearLayout>
<LinearLayout android:id="@+id/ll_emptylist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:weightSum="7">
<TextView
android:layout_weight="6"
android:layout_width="0dip"
android:layout_height="match_parent"
android:textSize="16sp"
android:textColor="#F00"
android:gravity="center"
android:layout_gravity="center"
android:text="@string/emptybaby"
/>
<ImageView android:id="@+id/iv_addbaby"
android:layout_weight="1"
android:layout_width="0dip"
android:layout_height="match_parent"
android:src="@drawable/add_black"/>
</LinearLayout>
<LinearLayout android:id="@+id/ll_notempty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout android:id="@+id/rl_usage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#A0000000"
>
<ImageView android:id="@+id/iv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/remove"
android:layout_alignParentRight="true"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/iv_close"
android:layout_alignParentLeft="true"
android:background="#00000000"
android:textColor="#FFF"
android:textSize="16sp"
android:gravity="center"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/babylist_usage"/>
</RelativeLayout>
<ListView android:id="@+id/lv_babylist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
</RelativeLayout>
這份 XML 文件應該不需要特別解說了,使用 ListView 標籤就可在版面中新增一個列表 (List)。我們把重心放在 BabyListActivity,其內容如下所示:
package lincyu.babylog.babymanager;
import android.app.ActionBar;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import java.util.ArrayList;
import lincyu.babylog.R;
import lincyu.babylog.db.Baby;
import lincyu.babylog.db.BabyDB;
public class BabyListActivity_20141016 extends ActionBarActivity {
private ListView lv_babylist;
private LinearLayout ll_emptylist, ll_loading, ll_notempty;
private ImageView iv_addbaby, iv_close;
private ArrayList<Baby> babylist;
private ArrayAdapter<Baby> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_babylist);
viewInitialization();
}
private void viewInitialization() {
ActionBar actionbar = getActionBar();
actionbar.setDisplayHomeAsUpEnabled(true);
actionbar.setDisplayShowHomeEnabled(false);
ll_loading = (LinearLayout)findViewById(R.id.ll_loading);
ll_emptylist = (LinearLayout)findViewById(R.id.ll_emptylist);
ll_notempty = (LinearLayout)findViewById(R.id.ll_notempty);
iv_addbaby = (ImageView)findViewById(R.id.iv_addbaby);
lv_babylist = (ListView)findViewById(R.id.lv_babylist);
}
@Override
public void onResume() {
super.onResume();
new BackgroundLoadBabyList().execute();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
break;
}
return super.onOptionsItemSelected(item);
}
protected class BackgroundLoadBabyList extends AsyncTask<Void, Void, Void> {
@Override
protected void onPostExecute(Void result) {
ll_loading.setVisibility(View.GONE);
if (babylist.size() == 0) {
ll_emptylist.setVisibility(View.VISIBLE);
ll_notempty.setVisibility(View.GONE);
} else {
ll_notempty.setVisibility(View.VISIBLE);
ll_emptylist.setVisibility(View.GONE);
}
lv_babylist.setAdapter(adapter);
}
@Override
protected void onPreExecute() {
ll_loading.setVisibility(View.VISIBLE);
ll_emptylist.setVisibility(View.GONE);
ll_notempty.setVisibility(View.GONE);
}
@Override
protected Void doInBackground(Void... params) {
babylist = BabyDB.getAllBabies(BabyListActivity_20141016.this);
adapter = new ArrayAdapter(BabyListActivity_20141016.this,
android.R.layout.simple_list_item_1, babylist);
return null;
}
}
}
和一般的 View 一樣,程式利用 Activity 類別的 findViewById 初始化 ListView 物件 lv_babylist,接著程式利用 AsyncTask [2] 載入 ListView 所需的資料,AsyncTask 可視為是 Thread 的另一種選擇,若 ListView 的資料需要從網路上下載,連線的動作必須寫在 Thread 於背景執行,另一種選擇是使用 AsyncTask 來實作。若對上述的說明不甚了解也沒有關係,只要記得用 AsyncTask 載入 ListView 資料時,讀取資料的動作 (有些情況可能需要連網) 應該寫在 doInBackground,在我們的例子中,我們呼叫 BabyDB.getAllBabies 取出所有寶寶資料,資料準備好後,我們要準備一個 Adapter,此處我們使用 ArrayAdapter [3]。
程式中使用的建構子是下面這一個:
ArrayAdapter(Context context, int resource, List<T> objects)
利用 ArrayAdapter 產生的 ListView 的每一個 Item 預設是一個 TextView,建構子的第二個參數是一個 Layout 檔的 Resource ID,程式中使用的是內建的 android.R.layout.simple_list_item_1,從 simple_list_item_1 的原始檔可以知道,這個 Layout 裏面就是一個 TextView,而這個 TextView 的 ID 是 「@android:id/text1」,明天我們會針對這個檔案做更詳細的說明,會介紹如何做一個更客製化的 ListView。第三個參數我們填上 babylist,其為一個 Baby 物件的動態陣列 (ArrayList<Baby>),每個動態陣列內的元素 (Baby物件) 會呼叫其 toString 方法將字串顯示於 TextView 上,筆者改寫了 Baby 物件的 toString 如下所示:
@Override
public String toString() {
int namelength = name.length();
int nicknamelength = nickname.length();
if (namelength == 0 && nicknamelength == 0) {
return "";
} else if (namelength == 0 && nicknamelength != 0) {
return nickname;
} else if (namelength != 0 && nicknamelength == 0) {
return name;
}
return nickname + " (" + name + ")";
}
利用 ArrayAdapter 產生了 adapter 物件後,最後只要呼叫ListView 的 setAdapter 就可以把資料和 ListView 綁定了。讀者可嘗試輸入幾筆資料,並觀看其執行結果,下面是一個範例:
參考資料
[1] 林致宇, Android程式設計入門與應用(附範例光碟), 全華出版社, ISBN: 9789572194126, http://www.opentech.com.tw/search/bookinfo.asp?isbn=9789572194126&companyID=04383129
[2] AsyncTask | Android Developers, http://developer.android.com/reference/android/os/AsyncTask.html
[3] ArrayAdapter | Android Developers, http://developer.android.com/reference/android/widget/ArrayAdapter.html